[Chapter Twenty-Four] [Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Twenty-Four
- 24.5.13 - An SGDI Driver for the Standard
Game Adapter Card
24.5.13 An SGDI Driver for the Standard Game Adapter Card
If you write your program to make SGDI calls, you will discover that
the TestPresence
call will probably return "not present"
when your program searches for a resident SGDI driver in memory. This is
because few manufacturers provide SGDI drivers at this point and even fewer
standard game adapter companies ship any software at all with their products,
much less an SGDI driver. Gee, what kind of standard is this if no one uses
it? Well, the purpose of this section is to rectify that problem.
The assembly code that appears at the end of this section provides a fully
functional, public domain, SGDI driver for the standard game adapter card
(the next section present an SGDI driver for the CH Products' Flightstick
Pro). This allows you to write your application making only SGDI calls.
By supplying the SGDI TSR with your product, your customers can use your
software with all standard joysticks. Later, if they purchase a specialized
device with its own SGDI driver, your software will automatically work with
that driver with no changes to your software.
If you do not like the idea of having a user run a TSR before your application,
you can always include the following code within your program's code space
and activate it if the SGDI TestPresence
call determines that
no other SGDI driver is present in memory when you start your program.
Here's the complete code for the standard game adapter SGDI driver:
.286
page 58, 132
name SGDI
title SGDI Driver for Standard Game Adapter Card
subttl This Program is Public Domain Material.
; SGDI.EXE
;
; Usage:
; SDGI
;
; This program loads a TSR which patches INT 15 so arbitrary game programs
; can read the joystick in a portable fashion.
;
;
; We need to load cseg in memory before any other segments!
cseg segment para public 'code'
cseg ends
; Initialization code, which we do not need except upon initial load,
; goes in the following segment:
Initialize segment para public 'INIT'
Initialize ends
; UCR Standard Library routines which get dumped later on.
.xlist
include stdlib.a
includelib stdlib.lib
.list
sseg segment para stack 'stack'
sseg ends
zzzzzzseg segment para public 'zzzzzzseg'
zzzzzzseg ends
CSEG segment para public 'CODE'
assume cs:cseg, ds:nothing
wp equ <word ptr>
byp equ <byte ptr>
Int15Vect dword 0
PSP word ?
; Port addresses for a typical joystick card:
JoyPort equ 201h
JoyTrigger equ 201h
; Data structure to hold information about each pot.
; (mainly for calibration and normalization purposes).
Pot struc
PotMask byte 0 ;Pot mask for hardware.
DidCal byte 0 ;Is this pot calibrated?
min word 5000 ;Minimum pot value
max word 0 ;Max pot value
center word 0 ;Pot value in the middle
Pot ends
; Variables for each of the pots. Must initialize the masks so they
; mask out all the bits except the incomming bit for each pot.
Pot0 Pot <1>
Pot1 Pot <2>
Pot2 Pot <4>
Pot3 Pot <8>
; The IDstring address gets passed back to the caller on a testpresence
; call. The four bytes before the IDstring must contain the serial number
; and current driver number.
SerialNumber byte 0,0,0
IDNumber byte 0
IDString byte "Standard SGDI Driver",0
byte "Public Domain Driver Written by Randall L. Hyde",0
;============================================================================
;
; ReadPots- AH contains a bit mask to determine which pots we should read.
; Bit 0 is one if we should read pot 0, bit 1 is one if we should
; read pot 1, bit 2 is one if we should read pot 2, bit 3 is one
; if we should read pot 3. All other bits will be zero.
;
; This code returns the pot values in SI, BX, BP, and DI for Pot 0, 1,
; 2, & 3.
;
ReadPots proc near
sub bp, bp
mov si, bp
mov di, bp
mov bx, bp
; Wait for any previous signals to finish up before trying to read this
; guy. It is possible that the last pot we read was very short. However,
; the trigger signal starts timers running for all four pots. This code
; terminates as soon as the current pot times out. If the user immediately
; reads another pot, it is quite possible that the new pot's timer has
; not yet expired from the previous read. The following loop makes sure we
; aren't measuring the time from the previous read.
mov dx, JoyPort
mov cx, 400h
Wait4Clean: in al, dx
and al, 0Fh
loopnz Wait4Clean
; Okay, read the pots. The following code triggers the 558 timer chip
; and then sits in a loop until all four pot bits (masked with the pot mask
; in AL) become zero. Each time through this loop that one or more of these
; bits contain zero, this loop increments the corresponding register(s).
mov dx, JoyTrigger
out dx, al ;Trigger pots
mov dx, JoyPort
mov cx, 1000h ;Don't let this go on forever.
PotReadLoop: in al, dx
and al, ah
jz PotReadDone
shr al, 1
adc si, 0 ;Increment SI if pot 0 still active.
shr al, 1
adc bx, 0 ;Increment BX if pot 1 still active.
shr al, 1
adc bp, 0 ;Increment BP if pot 2 still active.
shr al, 1
adc di, 0 ;Increment DI if pot 3 still active.
loop PotReadLoop ;Stop, eventually, if funny hardware.
and si, 0FFFh ;If we drop through to this point,
and bx, 0FFFh ; one or more pots timed out (usually
and bp, 0FFFh ; because they are not connected).
and di, 0FFFh ; The reg contains 4000h, set it to 0.
PotReadDone: ret
ReadPots endp
;----------------------------------------------------------------------------
;
; Normalize- BX contains a pointer to a pot structure, AX contains
; a pot value. Normalize that value according to the
; calibrated pot.
;
; Note: DS must point at cseg before calling this routine.
assume ds:cseg
Normalize proc near
push cx
; Sanity check to make sure the calibration process went okay.
cmp [bx].Pot.DidCal, 0 ;Is this pot calibrated?
je BadNorm ;If not, quit.
mov dx, [bx].Pot.Center ;Do a sanity check on the
cmp dx, [bx].Pot.Min ; min, center, and max
jbe BadNorm ; values to make sure
cmp dx, [bx].Pot.Max ; min < center < max.
jae BadNorm
; Clip the value if it is out of range.
cmp ax, [bx].Pot.Min ;If the value is less than
ja MinOkay ; the minimum value, set it
mov ax, [bx].Pot.Min ; to the minimum value.
MinOkay:
cmp ax, [bx].Pot.Max ;If the value is greater than
jb MaxOkay ; the maximum value, set it
mov ax, [bx].Pot.Max ; to the maximum value.
MaxOkay:
; Scale this guy around the center:
cmp ax, [bx].Pot.Center ;See if less than or greater
jb Lower128 ; than centered value.
; Okay, current reading is greater than the centered value, scale the reading
; into the range 128..255 here:
sub ax, [bx].Pot.Center
mov dl, ah ;Multiply by 128
mov ah, al
mov dh, 0
mov al, dh
shr dl, 1
rcr ax, 1
mov cx, [bx].Pot.Max
sub cx, [bx].Pot.Center
jz BadNorm ;Prevent division by zero.
div cx ;Compute normalized value.
add ax, 128 ;Scale to range 128..255.
cmp ah, 0
je NormDone
mov ax, 0ffh ;Result must fit in 8 bits!
jmp NormDone
; If the reading is below the centered value, scale it into the range
; 0..127 here:
Lower128: sub ax, [bx].Pot.Min
mov dl, ah
mov ah, al
mov dh, 0
mov al, dh
shr dl, 1
rcr ax, 1
mov cx, [bx].Pot.Center
sub cx, [bx].Pot.Min
jz BadNorm
div cx
cmp ah, 0
je NormDone
mov ax, 0ffh
jmp NormDone
; If something went wrong, return zero as the normalized value.
BadNorm: sub ax, ax
NormDone: pop cx
ret
Normalize endp
assume ds:nothing
;============================================================================
; INT 15h handler functions.
;============================================================================
;
; Although these are defined as near procs, they are not really procedures.
; The MyInt15 code jumps to each of these with BX, a far return address, and
; the flags sitting on the stack. Each of these routines must handle the
; stack appropriately.
;
;----------------------------------------------------------------------------
; BIOS- Handles the two BIOS calls, DL=0 to read the switches, DL=1 to
; read the pots. For the BIOS routines, we'll ignore the cooley
; switch (the hat) and simply read the other four switches.
BIOS proc near
cmp dl, 1 ;See if switch or pot routine.
jb Read4Sw
je ReadBIOSPots
; If not a valid BIOS call, jump to the original INT 15 handler and
; let it take care of this call.
pop bx
jmp cs:Int15Vect ;Let someone else handle it!
; BIOS read switches function.
Read4Sw: push dx
mov dx, JoyPort
in al, dx
and al, 0F0h ;Return only switch values.
pop dx
pop bx
iret
; BIOS read pots function.
ReadBIOSPots: pop bx ;Return a value in BX!
push si
push di
push bp
mov ah, 0Fh ;Read all four pots.
call ReadPots
mov ax, si
mov cx, bp ;BX already contains pot 1 reading.
mov dx, di
pop bp
pop di
pop si
iret
BIOS endp
;----------------------------------------------------------------------------
;
; ReadPot- On entry, DL contains a pot number to read.
; Read and normalize that pot and return the result in AL.
assume ds:cseg
ReadPot proc near
;;;;;;;;;; push bx ;Already on stack.
push ds
push cx
push dx
push si
push di
push bp
mov bx, cseg
mov ds, bx
; If dl = 0, read and normalize the value for pot 0, if not, try some
; other pot.
cmp dl, 0
jne Try1
mov ah, Pot0.PotMask ;Get bit for this pot.
call ReadPots ;Read pot 0.
lea bx, Pot0 ;Pointer to pot data.
mov ax, si ;Get pot 0 reading.
call Normalize ;Normalize to 0..FFh.
jmp GotPot ;Return to caller.
; Test for DL=1 here (read and normalize pot 1).
Try1: cmp dl, 1
jne Try2
mov ah, Pot1.PotMask
call ReadPots
mov ax, bx
lea bx, Pot1
call Normalize
jmp GotPot
; Test for DL=2 here (read and normalize pot 2).
Try2: cmp dl, 2
jne Try3
mov ah, Pot2.PotMask
call ReadPots
lea bx, Pot2
mov ax, bp
call Normalize
jmp GotPot
; Test for DL=3 here (read and normalize pot 3).
Try3: cmp dl, 3
jne BadPot
mov ah, Pot3.PotMask
call ReadPots
lea bx, Pot3
mov ax, di
call Normalize
jmp GotPot
; Bad value in DL if we drop to this point. The standard game card
; only supports four pots.
BadPot: sub ax, ax ;Pot not available, return zero.
GotPot: pop bp
pop di
pop si
pop dx
pop cx
pop ds
pop bx
iret
ReadPot endp
assume ds:nothing
;----------------------------------------------------------------------------
;
; ReadRaw- On entry, DL contains a pot number to read.
; Read that pot and return the unnormalized result in AX.
assume ds:cseg
ReadRaw proc near
;;;;;;;;;; push bx ;Already on stack.
push ds
push cx
push dx
push si
push di
push bp
mov bx, cseg
mov ds, bx
; This code is almost identical to the ReadPot code. The only difference
; is that we don't bother normalizing the result and (of course) we return
; the value in AX rather than AL.
cmp dl, 0
jne Try1
mov ah, Pot0.PotMask
call ReadPots
mov ax, si
jmp GotPot
Try1: cmp dl, 1
jne Try2
mov ah, Pot1.PotMask
call ReadPots
mov ax, bx
jmp GotPot
Try2: cmp dl, 2
jne Try3
mov ah, Pot2.PotMask
call ReadPots
mov ax, bp
jmp GotPot
Try3: cmp dl, 3
jne BadPot
mov ah, Pot3.PotMask
call ReadPots
mov ax, di
jmp GotPot
BadPot: sub ax, ax ;Pot not available, return zero.
GotPot: pop bp
pop di
pop si
pop dx
pop cx
pop ds
pop bx
iret
ReadRaw endp
assume ds:nothing
;----------------------------------------------------------------------------
; Read4Pots- Reads pots zero, one, two, and three returning their
; values in AL, AH, DL, and DH.
;
; On entry, AL contains the pot mask to select which pots
; we should read (bit 0=1 for pot 0, bit 1=1 for pot 1, etc).
Read4Pots proc near
;;;;;;;;;;; push bx ;Already on stack
push ds
push cx
push si
push di
push bp
mov dx, cseg
mov ds, dx
mov ah, al
call ReadPots
push bx ;Save pot 1 reading.
mov ax, si ;Get pot 0 reading.
lea bx, Pot0 ;Point bx at pot0 vars.
call Normalize ;Normalize.
mov cl, al ;Save for later.
pop ax ;Retreive pot 1 reading.
lea bx, Pot1
call Normalize
mov ch, al ;Save normalized value.
mov ax, bp
lea bx, Pot2
call Normalize
mov dl, al ;Pot 2 value.
mov ax, di
lea bx, Pot3
call Normalize
mov dh, al ;Pot 3 value.
mov ax, cx ;Pots 0 and 1.
pop bp
pop di
pop si
pop cx
pop ds
pop bx
iret
Read4Pots endp
;----------------------------------------------------------------------------
; CalPot- Calibrate the pot specified by DL. On entry, AL contains
; the minimum pot value (it better be less than 256!), BX
; contains the maximum pot value, and CX contains the centered
; pot value.
assume ds:cseg
CalPot proc near
pop bx ;Retrieve maximum value
push ds
push si
mov si, cseg
mov ds, si
; Sanity check on parameters, sort them in ascending order:
mov ah, 0
cmp bx, cx ;Make sure center < max
ja GoodMax
xchg bx, cx
GoodMax: cmp ax, cx ;Make sure min < center.
jb GoodMin ; (note: may make center<max).
xchg ax, cx
GoodMin: cmp cx, bx ;Again, be sure center < max.
jb GoodCenter
xchg cx, bx
GoodCenter:
; Okay, figure out who were supposed to calibrate:
lea si, Pot0
cmp dl, 1
jb DoCal ;Branch if this is pot 0
lea si, Pot1
je DoCal ;Branch if this is pot 1
lea si, Pot2
cmp dl, 3
jb DoCal ;Branch if this is pot 2
jne CalDone ;Branch if not pot 3
lea si, Pot3
DoCal: mov [si].Pot.min, ax ;Store away the minimum,
mov [si].Pot.max, bx ; maximum, and
mov [si].Pot.center, cx ; centered values.
mov [si].Pot.DidCal, 1 ;Note we've cal'd this pot.
CalDone: pop si
pop ds
iret
CalPot endp
assume ds:nothing
;----------------------------------------------------------------------------
; TestCal- Just checks to see if the pot specified by DL has already
; been calibrated.
assume ds:cseg
TestCal proc near
;;;;;;;; push bx ;Already on stack
push ds
mov bx, cseg
mov ds, bx
sub ax, ax ;Assume no calibration (also zeros AH)
lea bx, Pot0 ;Get the address of the specified
cmp dl, 1 ; pot's data structure into the
jb GetCal ; BX register.
lea bx, Pot1
je GetCal
lea bx, Pot2
cmp dl, 3
jb GetCal
jne BadCal
lea bx, Pot3
GetCal: mov al, [bx].Pot.DidCal
BadCal: pop ds
pop bx
iret
TestCal endp
assume ds:nothing
;----------------------------------------------------------------------------
;
; ReadSw- Reads the switch whose switch number appears in DL.
ReadSw proc near
;;;;;;; push bx ;Already on stack
push cx
sub ax, ax ;Assume no such switch.
cmp dl, 3 ;Return if the switch number is
ja NotDown ; greater than three.
mov cl, dl ;Save switch to read.
add cl, 4 ;Move from position four down to zero.
mov dx, JoyPort
in al, dx ;Read the switches.
shr al, cl ;Move desired switch bit into bit 0.
xor al, 1 ;Invert so sw down=1.
and ax, 1 ;Remove other junk bits.
NotDown: pop cx
pop bx
iret
ReadSw endp
;----------------------------------------------------------------------------
;
; Read16Sw- Reads all four switches and returns their values in AX.
Read16Sw proc near
;;;;;;;; push bx ;Already on stack
mov dx, JoyPort
in al, dx
shr al, 4
xor al, 0Fh ;Invert all switches.
and ax, 0Fh ;Set other bits to zero.
pop bx
iret
Read16Sw endp
;****************************************************************************
;
; MyInt15- Patch for the BIOS INT 15 routine to control reading the
; joystick.
MyInt15 proc far
push bx
cmp ah, 84h ;Joystick code?
je DoJoystick
OtherInt15: pop bx
jmp cs:Int15Vect
DoJoystick: mov bh, 0
mov bl, dh
cmp bl, 80h
jae VendorCalls
cmp bx, JmpSize
jae OtherInt15
shl bx, 1
jmp wp cs:jmptable[bx]
jmptable word BIOS
word ReadPot, Read4Pots, CalPot, TestCal
word ReadRaw, OtherInt15, OtherInt15
word ReadSw, Read16Sw
JmpSize = ($-jmptable)/2
; Handle vendor specific calls here.
VendorCalls: je RemoveDriver
cmp bl, 81h
je TestPresence
pop bx
jmp cs:Int15Vect
; TestPresence- Returns zero in AX and a pointer to the ID string in ES:BX
TestPresence: pop bx ;Get old value off stack.
sub ax, ax
mov bx, cseg
mov es, bx
lea bx, IDString
iret
; RemoveDriver- If there are no other drivers loaded after this one in
; memory, disconnect it and remove it from memory.
RemoveDriver:
push ds
push es
push ax
push dx
mov dx, cseg
mov ds, dx
; See if we're the last routine patched into INT 15h
mov ax, 3515h
int 21h
cmp bx, offset MyInt15
jne CantRemove
mov bx, es
cmp bx, wp seg MyInt15
jne CantRemove
mov ax, PSP ;Free the memory we're in
mov es, ax
push es
mov ax, es:[2ch] ;First, free env block.
mov es, ax
mov ah, 49h
int 21h
pop es ;Now free program space.
mov ah, 49h
int 21h
lds dx, Int15Vect ;Restore previous int vect.
mov ax, 2515h
int 21h
CantRemove: pop dx
pop ax
pop es
pop ds
pop bx
iret
MyInt15 endp
cseg ends
Initialize segment para public 'INIT'
assume cs:Initialize, ds:cseg
Main proc
mov ax, cseg ;Get ptr to vars segment
mov es, ax
mov es:PSP, ds ;Save PSP value away
mov ds, ax
mov ax, zzzzzzseg
mov es, ax
mov cx, 100h
meminit2
print
byte " Standard Game Device Interface driver",cr,lf
byte " PC Compatible Game Adapter Cards",cr,lf
byte " Written by Randall Hyde",cr,lf
byte cr,lf
byte cr,lf
byte "'SGDI REMOVE' removes the driver from memory",cr,lf
byte lf
byte 0
mov ax, 1
argv ;If no parameters, empty str.
stricmpl
byte "REMOVE",0
jne NoRmv
mov dh, 81h ;Remove opcode.
mov ax, 84ffh
int 15h ;See if we're already loaded.
test ax, ax ;Get a zero back?
jz Installed
print
byte "SGDI driver is not present in memory, REMOVE "
byte "command ignored.",cr,lf,0
mov ax, 4c01h ;Exit to DOS.
int 21h
Installed: mov ax, 8400h
mov dh, 80h ;Remove call
int 15h
mov ax, 8400h
mov dh, 81h ;TestPresence call
int 15h
cmp ax, 0
je NotRemoved
print
byte "Successfully removed SGDI driver from memory."
byte cr,lf,0
mov ax, 4c01h ;Exit to DOS.
int 21h
NotRemoved: print
byte "SGDI driver is still present in memory.",cr,lf,0
mov ax, 4c01h ;Exit to DOS.
int 21h
; Okay, Patch INT 15 and go TSR at this point.
NoRmv:
mov ax, 3515h
int 21h
mov wp Int15Vect, bx
mov wp Int15Vect+2, es
mov dx, cseg
mov ds, dx
mov dx, offset MyInt15
mov ax, 2515h
int 21h
mov dx, cseg
mov ds, dx
mov dx, seg Initialize
sub dx, ds:psp
add dx, 2
mov ax, 3100h ;Do TSR
int 21h
Main endp
Initialize ends
sseg segment para stack 'stack'
word 128 dup (0)
endstk word ?
sseg ends
zzzzzzseg segment para public 'zzzzzzseg'
byte 16 dup (0)
zzzzzzseg ends
end Main
The following program makes several different types of calls to an SGDI
driver. You can use this code to test out an SGDI TSR:
.xlist
include stdlib.a
includelib stdlib.lib
.list
cseg segment para public 'code'
assume cs:cseg, ds:nothing
MinVal0 word ?
MinVal1 word ?
MaxVal0 word ?
MaxVal1 word ?
; Wait4Button- Waits until the user presses and releases a button.
Wait4Button proc near
push ax
push dx
push cx
W4BLp: mov ah, 84h
mov dx, 900h ;Read the L.O. 16 buttons.
int 15h
cmp ax, 0 ;Any button down? If not,
je W4BLp ; loop until this is so.
xor cx, cx ;Debouncing delay loop.
Delay: loop Delay
W4nBLp: mov ah, 84h ;Now wait until the user releases
mov dx, 900h ; all buttons
int 15h
cmp ax, 0
jne W4nBLp
Delay2: loop Delay2
pop cx
pop dx
pop ax
ret
Wait4Button endp
Main proc
print
byte "SGDI Test Program.",cr,lf
byte "Written by Randall Hyde",cr,lf,lf
byte "Press any key to continue",cr,lf,0
getc
mov ah, 84h
mov dh, 4 ;Test presence call.
int 15h
cmp ax, 0 ;See if there
je MainLoop0
print
byte "No SGDI driver present in memory.",cr,lf,0
jmp Quit
MainLoop0: print
byte "BIOS: ",0
; Okay, read the switches and raw pot values using the BIOS compatible calls.
mov ah, 84h
mov dx, 0 ;BIOS compat. read switches.
int 15h
puth ;Output switch values.
mov al, ' '
putc
mov ah, 84h ;BIOS compat. read pots.
mov dx, 1
int 15h
putw
mov al, ' '
putc
mov ax, bx
putw
mov al, ' '
putc
mov ax, cx
putw
mov al, ' '
putc
mov ax, dx
putw
putcr
mov ah, 1 ;Repeat until key press.
int 16h
je MainLoop0
getc
; Read the minimum and maximum values for each pot from the user so we
; can calibrate the pots.
print
byte cr,lf,lf,lf
byte "Move joystick to upper left corner and press "
byte "any button.",cr,lf,0
call Wait4Button
mov ah, 84h
mov dx, 1 ;Read Raw Values
int 15h
mov MinVal0, ax
mov MinVal1, bx
print
byte cr,lf
byte "Move the joystick to the lower right corner "
byte "and press any button",cr,lf,0
call Wait4Button
mov ah, 84h
mov dx, 1 ;Read Raw Values
int 15h
mov MaxVal0, ax
mov MaxVal1, bx
; Calibrate the pots.
mov ax, MinVal0 ;Will be eight bits or less.
mov bx, MaxVal0
mov cx, bx ;Compute centered value as the
add cx, ax ; average of these two (this is
shr cx, 1 ; dangerous, but usually works!)
mov ah, 84h
mov dx, 300h ;Calibrate pot 0
int 15h
mov ax, MinVal1 ;Will be eight bits or less.
mov bx, MaxVal1
mov cx, bx ;Compute centered value as the
add cx, ax ; average of these two (this is
shr cx, 1 ; dangerous, but usually works!)
mov ah, 84h
mov dx, 301h ;Calibrate pot 1
int 15h
MainLoop1: print
byte "ReadSw: ",0
; Okay, read the switches and raw pot values using the BIOS compatible calls.
mov ah, 84h
mov dx, 800h ;Read switch zero.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 801h ;Read switch one.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 802h ;Read switch two.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 803h ;Read switch three.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 804h ;Read switch four
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 805h ;Read switch five.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 806h ;Read switch six.
int 15h
or al, '0'
putc
mov ah, 84h
mov dx, 807h ;Read switch seven.
int 15h ;We won't bother with
or al, '0' ; any more switches.
putc
mov al, ' '
putc
mov ah, 84h
mov dh, 9 ;Read all 16 switches.
int 15h
putw
print
byte " Pots: ",0
mov ax, 8403h ;Read joystick pots.
mov dx, 200h ;Read four pots.
int 15h
puth
mov al, ' '
putc
mov al, ah
puth
mov al, ' '
putc
mov ah, 84h
mov dx, 503h ;Raw read, pot 3.
int 15h
putw
putcr
mov ah, 1 ;Repeat until key press.
int 16h
je MainLoop1
getc
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
stk byte 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes byte 16 dup (?)
zzzzzzseg ends
end Main
- 24.5.13 - An SGDI Driver for
the Standard Game Adapter Card
Art of Assembly: Chapter Twenty-Four - 29 SEP 1996
[Chapter Twenty-Four][Previous] [Next]
[Art of Assembly][Randall
Hyde]